//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

#include <getopt.h>
#include <limits.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <errno.h>
#include <string.h>
#include <sys/syscall.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <syslog.h>
#include <locale.h>
#include "checksum.h"
#include "config.h"
#include "proc_utils.h"
#include "config.h"
#include "voter_logger.h"
#include "voter_customized.h"

// Options to debug different threads
//#define VOTING_MAIN_DEBUG       // SCHED_FIFO
#define VOTER_VOTING_MAIN_DEBUG   // SCHED_DEADLINE

extern int voting_main(void);
extern int voting_voter_main(void);
void *thread_func(void *data);
void *thread_voter_func(void *data);

int make_pthread_attr_SCHED_FIFO(pthread_attr_t *attr);
int make_pthread_attr_SCHED_DEADLINE(pthread_attr_t *attr);

void *thread_func(void *data)
{
    (void)data;
    voting_main();
    pthread_detach(pthread_self());
    return NULL;
}

void *thread_voter_func(void *data)
{
    (void)data;
    voting_voter_main();
    pthread_detach(pthread_self());
    return NULL;
}

/**
 * Parsing command line options
 * @param argc 
 * @param argv 
 * @return modRealtime (real-time threading mode flag)
 * 
 * -r:       Enable live thread mode
 * -v:       Display version information and exit
 * -p:       Specify TCP port
 * -t:       Specify UDP port
 * -h or -?: Display help information and exit
 */
int ParseCommandLineOptions(int argc, char *argv[])
{
    char *string = (char *)"rvhp::u::";
    int opt = getopt(argc, argv, string);
    int modRealtime = 0;

    // Handle each command line option
    while (-1 != opt) {
        switch (opt) {
            // Realtime thread
            case 'r':
                modRealtime = 1;
                break;
            // Show version
            case 'v':
                printf("como-fscp-voter v1.0.0.1\n");
                exit(0);
            // TCP port
            case 'p':
                g_iSERVER_PORT = atoi(optarg);
                break;
            // UDP port
            case 'u':
                g_iSERVER_PORT_UDP = atoi(optarg);
                break;
            // Help
            case '?':
            case 'h':
            default:
                printf("COMO Function Safety Computing Platform Voter.\n"
                       "Usage: como-fscp-voter [options]\n"
                       "Options:\n"
                       "  -h    Display command line options\n"
                       "  -p    TCP Port, default %d\n"
                       "  -t    UDP Port, default %d\n"
                       "  -v    Display version information\n"
                       "  -r    realtime thread\n",
                       SERVER_PORT, SERVER_PORT_UDP
                      );
                exit(0);
        }
        /*
        printf("opt = %c\t\t", opt);
        printf("optarg = %s\t\t", optarg);
        printf("optind = %d\t\t", optind);
        printf("argv[optind] = %s\n", argv[optind]);
        */
        opt = getopt(argc, argv, string);
    }
    
    return modRealtime;
}

/** https://www.cnblogs.com/charlieroro/p/12133100.html
 *
 * SCHED_FIFO: First in-first out scheduling(实时线程)
 * SCHED_FIFO仅适用于静态优先级大于0的线程，即当一个SCHED_FIFO的线程变为可运行
 * (runnable)状态时，它会立即抢占所有当前运行的SCHED_OTHER, SCHED_BATCH或SCHED_IDLE
 * 线程。SCHED_FIFO不使用时间片进行调度，所有使用SCHED_FIFO调度策略的线程应该遵守如下
 * 规则：
 * (1) 当一个运行中的SCHED_FIFO线程被其他有更高优先级的线程抢占后，该线程会返回到其优
 * 先级对应的列表的首部，当所有更高优先级的线程阻塞后，该线程将会立即恢复运行；
 * (2) 当一个阻塞的SCHED_FIFO 线程变为可运行状态时，该线程会返回到其优先级对应的列表
 * 末尾；
 * (3)如果调用 sched_setscheduler(2)，sched_setparam(2)，sched_setattr(2)，
 * pthread_setschedparam(3)，pthread_setschedprio(3) (通过pid)修改了正在运行或可
 * 运行状态的SCHED_FIFO线程的优先级时，该线程在列表中的位置取决于优先级的变动：
 *     - 如果线程优先级增加了，它将会放置到新优先级对应的列表末尾，同时可能抢占正在运
 *       行的具有相同优先级的线程；
 *     - 如果线程优先级没变，其在运行列表中的位置不变；
 *     - 如果线程优先级减小了，它将会放置到新优先级对应的列表的前面。
 *     根据POSIX.1-2008，通过非 pthread_setschedprio(3)方式来修改线程的优先级，可能
 *     会导致其放置到对应优先级列表的末尾。
 * (4) 调用了sched_yield(2) (用于释放CPU)的线程将会放置到列表末尾
 * SCHED_FIFO 线程将会一直运行，直到被更高优先级的线程抢占，或调用了sched_yield(2) 。
 */
int make_pthread_attr_SCHED_FIFO(pthread_attr_t *attr)
{
    int iRet;
    struct sched_param param;

    do {
        // Initialize pthread attributes (default values)
        iRet = pthread_attr_init(attr);
        if (0 != iRet) {
            syslog(LOG_ERR, "init pthread attributes failed");
            // goto out;
            break;
        }

        // Set a specific stack size
        iRet = pthread_attr_setstacksize(attr, PTHREAD_STACK_MIN + 16 * 1024 * 1024);
        if (0 != iRet) {
            syslog(LOG_ERR, "pthread setstacksize failed");
            // goto out;
            break;
        }

        // Set scheduler policy and priority of pthread
        iRet = pthread_attr_setschedpolicy(attr, SCHED_FIFO);
        if (0 != iRet) {
            syslog(LOG_ERR, "pthread setschedpolicy failed");
            // goto out;
            break;
        }
        param.sched_priority = 80;
        iRet = pthread_attr_setschedparam(attr, &param);
        if (0 != iRet) {
            syslog(LOG_ERR, "pthread setschedparam failed");
            // goto out;
            break;
        }
        // Use scheduling parameters of attr
        iRet = pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED);
        if (0 != iRet) {
            syslog(LOG_ERR, "pthread setinheritsched failed");
            // goto out;
            break;
        }
    } while (0);
//out:

    return iRet;
}

/** https://www.cnblogs.com/charlieroro/p/12133100.html
 *
 * SCHED_DEADLINE: Sporadic task model deadline scheduling
 * 3.14版本之后的Linux提供了一个新的调度策略SCHED_DEADLINE。该策略结合了GEDF(Global
 * Earliest Deadline First)和 CBS (Constant Bandwidth Server)。必须通
 * sched_setattr(2)和sched_getattr(2)来设置和获取该策略。
 *
 * 一个Sporadic task被定义为一系列任务，且每个任务每次仅激活一次。每个任务都有一个
 * relative deadline(该任务应该在该相对时间前停止运行)，以及一个computation time(执
 * 行该任务需要的CPU时间，对应下图的comp. time)。一个新的任务开始执行时会唤醒(wakeup)
 * 一个Sporadic task，该时间点被称为arrival time，start time为一个任务开始执行的时间，
 * absolute deadline(绝对截止时间)为arrival time加上relative deadline的时间点。
 *
           arrival/wakeup                    absolute deadline
                |    start time                    |
                |        |                         |
                v        v                         v
           -----x--------xooooooooooooooooo--------x--------x---
                         |<- comp. time ->|
                |<------- relative deadline ------>|
                |<-------------- period ------------------->|

 * 当一个SCHED_DEADLINE线程调用了sched_yield(2)将会停止当前任务，并等待新的周期。
 */
int make_pthread_attr_SCHED_DEADLINE(pthread_attr_t *attr)
{
do {
    // Initialize thread properties
    if (pthread_attr_init(attr) != 0) {
        syslog(LOG_ERR, "pthread_attr_init");
        // goto ret_SCHED_DEADLINE;
        break;
    }

    // Set the scheduling policy of the thread to SCHED_DEADLINE
    if (pthread_attr_setschedpolicy(attr, SCHED_DEADLINE) != 0) {
        syslog(LOG_ERR, "pthread_attr_setschedpolicy");
        // goto ret_SCHED_DEADLINE;
        break;
    }

    // Set the SCHED_DEADLINE parameter
    struct sched_attr {
        __u32 size;
        __u32 sched_policy;
        __u64 sched_flags;
        __u64 sched_nice;
        __u64 sched_priority;
        __u64 sched_runtime;
        __u64 sched_deadline;
        __u64 sched_period;
    } dl_param;

    dl_param.size = sizeof(dl_param);
    dl_param.sched_policy = SCHED_DEADLINE;
    dl_param.sched_flags = 0;
    dl_param.sched_runtime = g_pconfig->dl_param_sched_runtime;
    dl_param.sched_deadline = g_pconfig->dl_param_sched_deadline;
    dl_param.sched_period = g_pconfig->dl_param_sched_period;

    if (pthread_attr_setschedparam(attr, (struct sched_param *)&dl_param) != 0) {
        syslog(LOG_ERR, "pthread_attr_setschedparam");
        // goto ret_SCHED_DEADLINE;
        break;
    }
} while (0);
//ret_SCHED_DEADLINE:

    return 0;
}

// Pointer to the function that will be run when the thread starts executing,
// both the arg and return value are void*
typedef void *(*start_routine_t)(void *);

/**
 * Create thread (includes pthread_create)
 * 
 * @param thread         Point to (ID)        of created thread
 * @param attr           Point to (attribute) of thread
 * @param start_routine  Point to (function)  to be run when thread created
 * @param arg            Point to (args)      of start_routine function
 * @param name           Point to (name)      of thread
 * @param modRealtime    Flag of real-time
 * 
 * @return Return value of pthread_create()
 */
int CreateThread(pthread_t *thread, pthread_attr_t *attr,
                 start_routine_t start_routine, void *arg, const char *name,
                 int modRealtime)
{
    int iRet;
    
// WSL, pthread_create(&thread_voter, &attr... error
#ifndef _IN_WSL_LINUX_
    iRet = pthread_create(thread, attr, start_routine, arg);
#else
    (void)attr;
    iRet = 1;
#endif

    if (0 != iRet) {
        if (0 == modRealtime) {
            syslog(LOG_ERR, "create realtime `%s` failed, work in normal pthread", name);
            iRet = pthread_create(thread, NULL, start_routine, arg);
            if (0 != iRet) {
                *thread = 0;
                syslog(LOG_ERR, "create `%s` failed: %s", name, strerror(iRet));
            }
        }
    }

    return iRet;
}


int main(int argc, char* argv[])
{
    int modRealtime = 0;

    setlocale(LC_NUMERIC, "en_US.UTF-8");  // 强制使用美式本地化（逗号分隔）
    openlog("como_fscp_voter", LOG_PID | LOG_CONS, LOG_USER);

    //parse command line options
    modRealtime = ParseCommandLineOptions(argc, argv);
    
    load_config_file_default();

    // 关于表决器用户自定义的功能有关的调整
    g_pconfig->voter_OUTPUT_MSG = MyVoterOutputMsg;

    // Periodically check the integrity of g_pconfig data by calculating crc32
    g_pcomMetadata->crc_config = crc_32((const unsigned char *)&(g_pconfig),
                                                                sizeof(Config));

    /**
     * If necessary, check some setting of OS limits
     */
#if 0
    struct rlimit rl;
    getrlimit(RLIMIT_NPROC, &rl);
    printf("RLIMIT_NPROC: %ld\n", rl.rlim_cur);
#endif

    //(void)voter_sched_setattr();
    if (InitLog() < 0) {
        syslog(LOG_ERR, "InitLog failed!");
    }
#ifdef VOTING_MAIN_DEBUG
    pthread_attr_t attr_FIFO;
    pthread_t thread;
#endif
#ifdef VOTER_VOTING_MAIN_DEBUG
    pthread_attr_t attr_DEADLINE;
    pthread_t thread_voter;
#endif

    int iRet;

    do {
        /**
         * Lock memory
         */
        if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
            syslog(LOG_ERR, "mlockall failed: %m");
        }

        /**
         * Create realtime pthread
         */
        // Create pthread attributes
#ifdef VOTING_MAIN_DEBUG
        iRet = make_pthread_attr_SCHED_FIFO(&attr_FIFO);
#endif
#ifdef VOTER_VOTING_MAIN_DEBUG
        //iRet |= make_pthread_attr_SCHED_DEADLINE(&attr_DEADLINE);
        iRet = make_pthread_attr_SCHED_DEADLINE(&attr_DEADLINE);
#endif

        // Create threads
#ifdef VOTING_MAIN_DEBUG
        iRet |= CreateThread(&thread_voter, &attr_FIFO, thread_func, NULL,
                            "thread_func", modRealtime);
#endif
#ifdef VOTER_VOTING_MAIN_DEBUG
        //iRet |= CreateThread(&thread_voter, &attr_DEADLINE, thread_voter_func,
        //                    NULL, "thread_voter_func", modRealtime);
        iRet |= CreateThread(&thread_voter, &attr_DEADLINE, thread_func,
                            NULL, "thread_func", modRealtime);
#endif
        if (0 != iRet) {
            syslog(LOG_ERR, "CreateThread failed");
        }

#ifdef SYNC_WITH_PHXPAXOS
        iRet = PreparePhxPaxos(MyVoterPhxPaxosSmCallback, NULL);
        if (0 != iRet) {
            syslog(LOG_ERR, "PreparePhxPaxos failed");
        }
#endif

        /**
         * pthread_join 会阻塞调用它的线程（通常是主线程），直到指定的目标线程退出。
         */
        // thread_func
#ifdef VOTING_MAIN_DEBUG
        if (0 != thread) {
            iRet = pthread_join(thread, NULL);
            if (0 != iRet) {
                syslog(LOG_ERR, "join `thread_func` failed: %s", strerror(errno));
            }
        }
#endif
        // thread_voter_func
#ifdef VOTER_VOTING_MAIN_DEBUG
        if (0 != thread_voter) {
            iRet = pthread_join(thread_voter, NULL);
            if (0 != iRet) {
                syslog(LOG_ERR, "join `thread_voter_func` failed: %s", strerror(errno));
            }
        }
#endif

        /**
         * Destory pthread attributes
         */
#ifdef VOTING_MAIN_DEBUG
        pthread_attr_destroy(&attr_FIFO);
#endif
#ifdef VOTER_VOTING_MAIN_DEBUG
        pthread_attr_destroy(&attr_DEADLINE);
#endif

    } while (0);
//out:

    return iRet;
}
